/* QtLua -- Lua bindings for Qt
   Copyright (C) 2011, Jarek Pelczar

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General
   Public License along with this library; if not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301 USA

*/

#include "qluascriptvalue.h"
#include "qluascriptvalue_p.h"
#include "qluascriptengine_p.h"
#include "qluametaobjectwrapper_p.h"
#include "qluaqobjectwrapper_p.h"
#include <QStringList>

QLuaScriptValueData::QLuaScriptValueData()
    : m_listNode(this),
      m_engine(0)
{
}

QLuaScriptValueData::QLuaScriptValueData(const QLuaScriptValueData& other)
    : m_listNode(this),
      m_engine(other.m_engine),
      m_type(other.m_type)
{
    if(m_engine) {
        m_engine->m_values.insertNodeAfterThis(&m_listNode);
    }
}

QLuaScriptValueData::~QLuaScriptValueData()
{
    clear();
}

void QLuaScriptValueData::clear()
{
    if(m_type != L_TNONE)
    {
        if(m_type == L_TTABLE ||
           m_type == L_TFUNCTION ||
           m_type == L_TTHREAD ||
           m_type == L_TUSERDATA)
        {
            Q_ASSERT_X(m_engine != NULL, "QLuaScriptValueData::clear()", "Lua engine pointer is NULL");
            lua_State * L = m_engine->L;
            Q_ASSERT_X(L != NULL, "QLuaScriptValueData::clear()", "Deleting referenced object with deleted Lua engine");
//#ifdef QT_DEBUG
//            qDebug() << ">>> Put reference" << u.m_handle;
//#endif
            lua_unref(L, u.m_handle);
        }

        u.m_handle = -1;
        m_str.clear();
        m_type = L_TNONE;
    }
}

QVariant QLuaScriptValueData::stackRefToVariant(const QLuaScriptValue * v_ptr) const
{
    lua_State * L = m_engine->L;
    Q_ASSERT_X(L != NULL, "QLuaScriptValueData::stackRefToVariant()", "Trying to access stack referenced object with deleted Lua engine");
    int type = lua_type(L, u.m_handle);
    switch(type)
    {
    case LUA_TNONE:
        return QVariant();
    case LUA_TNIL:
        return QVariant();
    case LUA_TBOOLEAN:
        return QVariant(lua_toboolean(L, u.m_handle));
    case LUA_TLIGHTUSERDATA:
        return qVariantFromValue<void *>(lua_touserdata(L, u.m_handle));
    case LUA_TUSERDATA:
    case LUA_TTABLE:
    case LUA_TFUNCTION:
    case LUA_TTHREAD:
        return qVariantFromValue(*v_ptr);
    case LUA_TNUMBER:
        return QVariant(lua_tonumber(L, u.m_handle));
    case LUA_TSTRING: {
        size_t len = 0;
        const char * ptr = lua_tolstring(L, u.m_handle, &len);
        return QVariant(QByteArray(ptr, len));
        }
    default:
        Q_ASSERT_X(0, "QLuaScriptValueData::stackRefToVariant()", "Invalid lua variable type on stack");
        return QVariant();
    }
}

QVariant QLuaScriptValueData::toVariant(const QLuaScriptValue * v_ptr) const
{
    switch(m_type)
    {
    case L_TNONE:
    case L_TNIL:
        return QVariant();
    case L_TBOOLEAN:
        return QVariant(u.m_bool);
    case L_TLIGHTUSERDATA:
        return qVariantFromValue<void *>(u.m_ptr);
    case L_TNUMBER:
        return qVariantFromValue(u.m_real);
    case L_TSTRING:
        return qVariantFromValue(m_str);
    case L_TTABLE:
    case L_TFUNCTION:
    case L_TUSERDATA:
    case L_TTHREAD:
        return qVariantFromValue(*v_ptr);
    case L_TINTERGER:
        return qVariantFromValue(u.m_int);
    default:
        Q_ASSERT_X(0, "QLuaScriptValueData::toVariant()", "Invalid lua variable type");
        return QVariant();
    }
}

void QLuaScriptValueData::push(lua_State * L)
{
    switch(m_type)
    {
    case L_TSTACKREF:
        lua_pushvalue(L, u.m_handle);
        break;
    case L_TNONE:
        qWarning("[QtLua] Trying to push NONE value to stack");
        break;
    case L_TNIL:
        lua_pushnil(L);
        break;
    case L_TBOOLEAN:
        lua_pushboolean(L, u.m_bool);
        break;
    case L_TLIGHTUSERDATA:
        lua_pushlightuserdata(L, u.m_ptr);
        break;
    case L_TNUMBER:
        lua_pushnumber(L, u.m_real);
        break;
    case L_TSTRING:
        lua_pushlstring(L, m_str.constData(), m_str.length());
        break;
    case L_TTABLE:
    case L_TFUNCTION:
    case L_TUSERDATA:
    case L_TTHREAD:
//#ifdef QT_DEBUG
//        qDebug() << ">>> Get reference" << u.m_handle;
//#endif
        lua_getref(L, u.m_handle);
        break;
    case L_TINTERGER:
        lua_pushinteger(L, u.m_int);
        break;
    default:
        qFatal("[QtLua] Panic with value type %d", m_type);
        Q_ASSERT_X(0, "QLuaScriptValueData::push()", "Invalid lua variable type");
    }
}

void QLuaScriptValueData::fromStack(int index)
{
    Q_ASSERT_X(m_engine != NULL, "QLuaScriptValueData::fromStack", "QLuaScriptEngine is NULL");
    lua_State * L = m_engine->L;
    Q_ASSERT_X(L != NULL, "QLuaScriptValueData::fromStack", "lua_State is NULL");
    clear();

//#ifdef QT_DEBUG
//    qDebug() << "[QtLua] fromStack index =" << index << "Type =" << lua_type(L, index);
//#endif

    switch(lua_type(L, index))
    {
    case LUA_TNONE:
        m_type = L_TNONE;
        break;
    case LUA_TNIL:
        m_type = L_TNIL;
        break;
    case LUA_TBOOLEAN:
        m_type = L_TBOOLEAN;
        u.m_bool = lua_toboolean(L, index);
        break;
    case LUA_TLIGHTUSERDATA:
        m_type = L_TLIGHTUSERDATA;
        u.m_ptr = lua_touserdata(L, index);
        break;
    case LUA_TUSERDATA:
    case LUA_TTABLE:
    case LUA_TFUNCTION:
    case LUA_TTHREAD:
        m_type = lua_type(L, index);
        lua_pushvalue(L, index);                        
        u.m_handle = lua_ref(L, 1);
//#ifdef QT_DEBUG
//        qDebug() << "<<< Lua ref" << u.m_handle;
//#endif
        break;
    case LUA_TNUMBER:
        m_type = L_TNUMBER;
        u.m_real = lua_tonumber(L, index);
        break;
    case LUA_TSTRING: {
        size_t len = 0;
        const char * ptr = lua_tolstring(L, index, &len);
        m_type = L_TSTRING;
        m_str = QByteArray(ptr, len);
        }
        break;
    default:
        Q_ASSERT_X(0, "QLuaScriptValueData::fromStack()", "Invalid lua variable type");
    }
}

void QLuaScriptValueData::refStack(int index)
{
    Q_ASSERT_X(m_engine != NULL, "QLuaScriptValueData::refStack", "QLuaScriptEngine is NULL");
    lua_State * L = m_engine->L;
    Q_ASSERT_X(L != NULL, "QLuaScriptValueData::refStack", "lua_State is NULL");
    clear();

    if(index < 0) {
        index = lua_gettop(L) + index + 1;
    }

    m_type = L_TSTACKREF;
    u.m_handle = index;
}

QLuaScriptValue QLuaScriptValueData::newEmpty()
{
    Q_ASSERT_X(m_engine != NULL, "QLuaScriptValueData::newEmpty()", "Trying to call newEmpty() on NULL engine");

    QLuaScriptValue rv;
    rv.data = new QLuaScriptValueData;
    rv.data->m_engine = m_engine;

    m_engine->m_values.insertNodeAfterThis(&rv.data->m_listNode);
    return rv;
}

QLuaScriptValue::QLuaScriptValue() : data(0)
{
}

QLuaScriptValue::QLuaScriptValue(const QLuaScriptValue &rhs) : data(rhs.data)
{
}

QLuaScriptValue &QLuaScriptValue::operator=(const QLuaScriptValue &rhs)
{
    if (this != &rhs)
        data.operator=(rhs.data);
    return *this;
}

QLuaScriptValue::~QLuaScriptValue()
{
}

QLuaScriptEngine * QLuaScriptValue::engine() const
{
    return data ? (data->m_engine ? data->m_engine->q_ptr : NULL) : NULL;
}

QLuaScriptValue::QLuaScriptValue(bool value) : data(new QLuaScriptValueData)
{
    data->m_type = QLuaScriptValueData::L_TBOOLEAN;
    data->u.m_bool = value;
}

QLuaScriptValue::QLuaScriptValue(int value) : data(new QLuaScriptValueData)
{
    data->m_type = QLuaScriptValueData::L_TINTERGER;
    data->u.m_int = value;
}


QLuaScriptValue::QLuaScriptValue(qreal value) : data(new QLuaScriptValueData)
{
    data->m_type = QLuaScriptValueData::L_TNUMBER;
    data->u.m_real = value;
}

QLuaScriptValue::QLuaScriptValue(const QString& value) : data(new QLuaScriptValueData)
{
    data->m_type = QLuaScriptValueData::L_TSTRING;
    data->m_str = value.toUtf8();
}

QLuaScriptValue::QLuaScriptValue(const QLatin1String& value) : data(new QLuaScriptValueData)
{
    data->m_type = QLuaScriptValueData::L_TSTRING;
    data->m_str = QByteArray::fromRawData(value.latin1(), qstrlen(value.latin1()));
}

QLuaScriptValue::QLuaScriptValue(const char * value) : data(new QLuaScriptValueData)
{
    data->m_type = QLuaScriptValueData::L_TSTRING;
    data->m_str = value;
}

QLuaScriptValue::QLuaScriptValue(const QByteArray& value) : data(new QLuaScriptValueData)
{
    data->m_type = QLuaScriptValueData::L_TSTRING;
    data->m_str = value;
}

QLuaScriptValue::QLuaScriptValue(const char * value, int length, StringWrapMode wrapMode) : data(new QLuaScriptValueData)
{
    data->m_type = QLuaScriptValueData::L_TSTRING;

    if(wrapMode == WrapCopy)
        data->m_str = QByteArray(value, length);
    else
        data->m_str = QByteArray::fromRawData(value, length);
}

QVariant QLuaScriptValue::toVariant() const
{
    if(!data)
        return QVariant();
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
        return data->stackRefToVariant(this);
    return data->toVariant(this);
}

QLuaScriptValue::QLuaScriptValue(SpecialValue value)
{
    if(value == NilValue) {
        data = new QLuaScriptValueData;
        data->m_type = LUA_TNIL;
    }
}

QLuaScriptValue QLuaScriptValue::call(const QLuaScriptValueList& args)
{
    if(!data || !data->m_engine)
        return QLuaScriptValue();
    lua_State * L = data->m_engine->L;
    Q_ASSERT_X(L != NULL, "QLuaScriptValue::call", "Trying to invoke method on deleted lua state");

    lua_pushlightuserdata(L, (void *)data->m_engine);
    lua_pushcclosure(L, QLuaScriptEnginePrivate::invoke_traceback, 1);

    int cur_top = lua_gettop(L);

    data->push(L);

    for(int i = 0 ; i < args.count() ; ++i)
    {
        if(args[i].isValid())
            args[i].data->push(L);
        else {
            qWarning("[QtLua] Pushing invalid argument on the stack");
            lua_pop(L, lua_gettop(L) - cur_top);
            return QLuaScriptValue();
        }
    }

    int result = lua_pcall(L, args.count(), LUA_MULTRET, cur_top - 1);

    if(!data ||  !data->m_engine || !data->m_engine->L)
    {
        qWarning("[QtLua] Lua engine deleted while calling the function !!!");
        return QLuaScriptValue();
    }

    if(result != 0)
        lua_gc(L, LUA_GCCOLLECT, 0);

    if(result == LUA_ERRRUN)
    {
#ifdef QT_DEBUG
        qWarning() << "[QtLua] Runtime error detected while calling function:" << lua_tostring(L, -1);
#endif
        lua_pop(L, 2);
        return QLuaScriptValue();
    } else if(result == LUA_ERRMEM) {
#ifdef QT_DEBUG
        qWarning() << "[QtLua] Out of memory detected while calling function:" << lua_tostring(L, -1);
#endif
        lua_pop(L, 2);
        return QLuaScriptValue();
    } else if(result == LUA_ERRSYNTAX) {
#ifdef QT_DEBUG
        qWarning() << "[QtLua] Syntax error detected while calling function:" << lua_tostring(L, -1);
#endif
        lua_pop(L, 2);
        return QLuaScriptValue();
    } else if(result != 0) {
#ifdef QT_DEBUG
        qWarning() << "[QtLua] Error detected while calling function:" << lua_tostring(L, -1);
#endif
        lua_pop(L, 2);
        return QLuaScriptValue();
    }

    int new_top = lua_gettop(L);

    int nRetArgs = new_top - cur_top;

    if(nRetArgs == 0) {
        lua_pop(L, 1);
        return QLuaScriptValue();
    }

    if(nRetArgs == 1)
    {
        QLuaScriptValue value(data->newEmpty());
        value.data->fromStack(-1);
        lua_pop(L, 2);
        return value;
    }

    QLuaScriptValue table(data->m_engine->q_ptr->newTable());

    lua_pop(L, nRetArgs + 1);

    return table;
}

bool QLuaScriptValue::isValid() const
{
    if(!data)
        return false;
    if(data->m_type == LUA_TNONE)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type != LUA_TNONE;
    }
    if(data->m_type == LUA_TTHREAD || data->m_type == LUA_TUSERDATA ||
       data->m_type == LUA_TFUNCTION || data->m_type == LUA_TTABLE)
    {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
    }
    return true;
}

bool QLuaScriptValue::isNil() const
{
    if(!data)
        return true;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return true;
        lua_State * L = data->m_engine->L;
        if(!L)
            return true;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TNIL || type == LUA_TNONE;
    }
    return data->m_type == LUA_TNIL || data->m_type == LUA_TNONE;
}

bool QLuaScriptValue::isBool () const
{
    if(!data)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TBOOLEAN;
    }
    return data->m_type == LUA_TBOOLEAN;
}

bool QLuaScriptValue::isFunction() const
{
    if(!data)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TFUNCTION;
    }
    return data->m_type == LUA_TFUNCTION && data->m_engine != NULL && data->m_engine->L != NULL;
}

bool QLuaScriptValue::isNumber() const
{
    if(!data)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TNUMBER;
    }
    return data->m_type == LUA_TNUMBER || data->m_type == QLuaScriptValueData::L_TINTERGER;
}

bool QLuaScriptValue::isString() const
{
    if(!data)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TSTRING;
    }
    return data->m_type == LUA_TSTRING;
}

bool QLuaScriptValue::isTable() const
{
    if(!data)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TTABLE;
    }
    return data->m_type == LUA_TTABLE && data->m_engine != NULL && data->m_engine->L != NULL;
}

bool QLuaScriptValue::isUserData() const
{
    if(!data)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TUSERDATA;
    }
    return data->m_type == LUA_TUSERDATA && data->m_engine != NULL && data->m_engine->L != NULL;
}

bool QLuaScriptValue::isLightUserData() const
{
    if(!data)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TLIGHTUSERDATA;
    }
    return data->m_type == LUA_TLIGHTUSERDATA;
}

bool QLuaScriptValue::isThread() const
{
    if(!data)
        return false;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine)
            return false;
        lua_State * L = data->m_engine->L;
        if(!L)
            return false;
        int type = lua_type(L, data->u.m_handle);
        return type == LUA_TTHREAD;
    }
    return data->m_type == LUA_TTHREAD && data->m_engine != NULL && data->m_engine->L != NULL;
}

bool QLuaScriptValue::isMetaObject() const
{
    if(!data || !data->m_engine || !data->m_engine->L)
        return false;
    void * ptr = toUserData();

    return data->m_engine->m_ptrMetaTypes[ptr] == qMetaTypeId<QLuaInternal::QLuaMetaObjectWrapper>();
}

bool QLuaScriptValue::isQObject() const
{
    if(!data || !data->m_engine || !data->m_engine->L)
        return false;
    void * ptr = toUserData();
    if(data->m_engine->m_ptrMetaTypes[ptr] != qMetaTypeId<QLuaInternal::QLuaQObjectWrapper>())
        return false;
    QLuaInternal::QLuaQObjectWrapper * wrapper = (QLuaInternal::QLuaQObjectWrapper *)ptr;
    return !wrapper->m_objectPtr.isNull();
}

bool QLuaScriptValue::isObject() const
{
    if(!data || !data->m_engine || !data->m_engine->L)
        return false;
    void * ptr = toUserData();
    return data->m_engine->m_ptrMetaTypes[ptr] == qMetaTypeId<QLuaInternal::QLuaQObjectWrapper>();
}

QObject * QLuaScriptValue::toQObject() const
{
    if(!data || !data->m_engine || !data->m_engine->L)
        return NULL;
    void * ptr = toUserData();
    if(data->m_engine->m_ptrMetaTypes[ptr] != qMetaTypeId<QLuaInternal::QLuaQObjectWrapper>()) {
//#ifdef QT_DEBUG
//        qDebug() << "meta type is" << data->m_engine->m_ptrMetaTypes[ptr];
//#endif
        return NULL;
    }

    QLuaInternal::QLuaQObjectWrapper * wrapper = (QLuaInternal::QLuaQObjectWrapper *)ptr;

    return wrapper->m_objectPtr;
}

const QMetaObject * QLuaScriptValue::toMetaObject() const
{
    if(!data || !data->m_engine || !data->m_engine->L)
        return NULL;
    void * ptr = toUserData();
    if(!ptr)
        return NULL;
    if(data->m_engine->m_ptrMetaTypes[ptr] != qMetaTypeId<QLuaInternal::QLuaMetaObjectWrapper>())
        return NULL;
    return ((QLuaInternal::QLuaMetaObjectWrapper *)ptr)->m_metaObject;
}

QLuaScriptValue QLuaScriptValue::newFromStack(QLuaScriptEngine * engine, int index)
{
    if(!engine) {
        qWarning("[QtLua] QLuaScriptValue::newFromStack() called with NULL engine");
        return QLuaScriptValue();
    }

    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);

    QLuaScriptValue rv;
    rv.data = new QLuaScriptValueData;
    rv.data->m_engine = priv;
    priv->m_values.insertNodeAfterThis(&rv.data->m_listNode);

    rv.data->fromStack(index);

    return rv;
}

static int qtlua_get_property_protected(lua_State * L)
{
    lua_gettable(L, 1);
    return 1;
}

static int qtlua_set_property_protected(lua_State * L)
{
    lua_settable(L, 1);
    return 0;
}

QLuaScriptValue QLuaScriptValue::property(int index) const
{
    if(!data)
        return QLuaScriptValue(QLuaScriptValue::NilValue);
    QLuaScriptValue rv;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
            return QLuaScriptValue(QLuaScriptValue::NilValue);

        lua_pushcfunction(L, qtlua_get_property_protected);
        lua_pushvalue(L, data->u.m_handle);
        lua_pushinteger(L, index);

        int pcall_result = lua_pcall(L, 2, 1, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return QLuaScriptValue();
        }

        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);                
        lua_State * L = data->m_engine->L;

        lua_pushcfunction(L, qtlua_get_property_protected);
        data->push(L);
        lua_pushinteger(L, index);

        int pcall_result = lua_pcall(L, 2, 1, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return QLuaScriptValue();
        }

        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    }

    return rv;
}
QLuaScriptValue QLuaScriptValue::property(const QByteArray& key) const
{
    if(!data)
        return QLuaScriptValue(QLuaScriptValue::NilValue);
    QLuaScriptValue rv;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
            return QLuaScriptValue(QLuaScriptValue::NilValue);

        lua_pushcfunction(L, qtlua_get_property_protected);
        lua_pushvalue(L, data->u.m_handle);
        lua_pushlstring(L, key.constData(), key.length());

        int pcall_result = lua_pcall(L, 2, 1, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return QLuaScriptValue();
        }

        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;

        lua_pushcfunction(L, qtlua_get_property_protected);
        data->push(L);
        lua_pushlstring(L, key.constData(), key.length());

        int pcall_result = lua_pcall(L, 2, 1, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return QLuaScriptValue();
        }

        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    }

    return rv;
}

QLuaScriptValue QLuaScriptValue::property(const char * key) const
{
    if(!data || !key)
        return QLuaScriptValue(QLuaScriptValue::NilValue);
    QLuaScriptValue rv;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
            return QLuaScriptValue(QLuaScriptValue::NilValue);

        lua_pushcfunction(L, qtlua_get_property_protected);
        lua_pushvalue(L, data->u.m_handle);
        lua_pushlstring(L, key, qstrlen(key));

        int pcall_result = lua_pcall(L, 2, 1, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return QLuaScriptValue();
        }

        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;

        lua_pushcfunction(L, qtlua_get_property_protected);
        data->push(L);
        lua_pushlstring(L, key, qstrlen(key));

        int pcall_result = lua_pcall(L, 2, 1, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return QLuaScriptValue();
        }

        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    }

    return rv;
}

QLuaScriptValue QLuaScriptValue::rawProperty(int index) const
{
    if(!data)
        return QLuaScriptValue(QLuaScriptValue::NilValue);
    QLuaScriptValue rv;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_pushinteger(L, index);
        lua_rawget(L, data->u.m_handle);
        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        data->push(L);
        lua_pushinteger(L, index);
        lua_rawget(L, -2);
        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 2);
    }

    return rv;
}

QLuaScriptValue QLuaScriptValue::rawProperty(const QByteArray& key) const
{
    if(!data)
        return QLuaScriptValue(QLuaScriptValue::NilValue);
    QLuaScriptValue rv;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_pushlstring(L, key.constData(), key.length());
        lua_rawget(L, data->u.m_handle);
        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        data->push(L);
        lua_pushlstring(L, key.constData(), key.length());
        lua_rawget(L, -2);
        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 2);
    }

    return rv;
}

QLuaScriptValue QLuaScriptValue::rawProperty(const char * key) const
{
    if(!data || !key)
        return QLuaScriptValue(QLuaScriptValue::NilValue);
    QLuaScriptValue rv;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_pushlstring(L, key, qstrlen(key));
        lua_rawget(L, data->u.m_handle);
        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 1);
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return QLuaScriptValue(QLuaScriptValue::NilValue);
        lua_State * L = data->m_engine->L;
        data->push(L);
        lua_pushlstring(L, key, qstrlen(key));
        lua_rawget(L, -2);
        rv = newFromStack(data->m_engine->q_ptr, -1);
        lua_pop(L, 2);
    }

    return rv;
}

bool QLuaScriptValue::setProperty(int index, const QLuaScriptValue& value)
{
    if(!data)
        return false;
    if(!value.isValid())
        return false;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
        {
            qWarning("[QtLua] Trying to set index in invalid variable");
            return false;
        }

        lua_pushcfunction(L, qtlua_set_property_protected);
        lua_pushvalue(L, data->u.m_handle);
        lua_pushinteger(L, index);
        value.data->push(L);

        int pcall_result = lua_pcall(L, 3, 0, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return false;
        }

        return true;
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;

        lua_pushcfunction(L, qtlua_set_property_protected);
        data->push(L);
        lua_pushinteger(L, index);
        value.data->push(L);

        int pcall_result = lua_pcall(L, 3, 0, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return false;
        }

        return true;
    } else {
        qWarning("[QtLua] Trying to set index in invalid variable");
    }

    return false;
}

bool QLuaScriptValue::setProperty(const QByteArray& key, const QLuaScriptValue& value)
{
    if(!data)
        return false;
    if(!value.isValid())
        return false;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
        {
            qWarning("[QtLua] Trying to set index in invalid variable");
            return false;
        }

        lua_pushcfunction(L, qtlua_set_property_protected);
        lua_pushvalue(L, data->u.m_handle);
        lua_pushlstring(L, key.constData(), key.length());
        value.data->push(L);

        int pcall_result = lua_pcall(L, 3, 0, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return false;
        }

        return true;
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;

        lua_pushcfunction(L, qtlua_set_property_protected);
        data->push(L);
        lua_pushlstring(L, key.constData(), key.length());
        value.data->push(L);

        int pcall_result = lua_pcall(L, 3, 0, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return false;
        }

        return true;
    } else {
        qWarning("[QtLua] Trying to set index in invalid variable");
    }

    return false;
}

bool QLuaScriptValue::setProperty(const char * key, const QLuaScriptValue& value)
{
    if(!data || !key)
        return false;
    if(!value.isValid())
        return false;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE && type != LUA_TUSERDATA)
        {
            qWarning("[QtLua] Trying to set index in invalid variable");
            return false;
        }

        lua_pushcfunction(L, qtlua_set_property_protected);
        lua_pushvalue(L, data->u.m_handle);
        lua_pushlstring(L, key, qstrlen(key));
        value.data->push(L);

        int pcall_result = lua_pcall(L, 3, 0, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return false;
        }

        return true;
    } else if(data->m_type == LUA_TUSERDATA || data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;

        lua_pushcfunction(L, qtlua_set_property_protected);
        data->push(L);
        lua_pushlstring(L, key, qstrlen(key));
        value.data->push(L);

        int pcall_result = lua_pcall(L, 3, 0, 0);

        if(pcall_result != 0) {
            QLuaException ex(data->m_engine->q_ptr->newException(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1)));
            lua_pop(L, 1);
            data->m_engine->m_lastException = ex;
            lua_gc(L, LUA_GCCOLLECT, 0);
            return false;
        }

        return true;
    } else {
        qWarning("[QtLua] Trying to set index in invalid variable");
    }

    return false;
}

bool QLuaScriptValue::setRawProperty(int index, const QLuaScriptValue& value)
{
    if(!data)
        return false;
    if(!value.isValid())
        return false;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE)
        {
            qWarning("[QtLua] Trying to set index in invalid variable");
            return false;
        }
        lua_pushinteger(L, index);
        value.data->push(L);
        lua_rawset(L, data->u.m_handle);
        return true;
    } else if(data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        data->push(L);
        lua_pushinteger(L, index);
        value.data->push(L);
        lua_rawset(L, -3);
        lua_pop(L, 1);
        return true;
    } else {
        qWarning("[QtLua] Trying to set index in invalid variable");
    }
    return false;
}

bool QLuaScriptValue::setRawProperty(const QByteArray& key, const QLuaScriptValue& value)
{
    if(!data)
        return false;
    if(!value.isValid())
        return false;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE)
        {
            qWarning("[QtLua] Trying to set index in invalid variable");
            return false;
        }
        lua_pushlstring(L, key.constData(), key.length());
        value.data->push(L);
        lua_rawset(L, data->u.m_handle);
        return true;
    } else if(data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        data->push(L);
        lua_pushlstring(L, key.constData(), key.length());
        value.data->push(L);
        lua_rawset(L, -3);
        lua_pop(L, 1);
        return true;
    } else {
        qWarning("[QtLua] Trying to set index in invalid variable");
    }
    return false;
}

bool QLuaScriptValue::setRawProperty(const char * key, const QLuaScriptValue& value)
{
    if(!data || !key)
        return false;
    if(!value.isValid())
        return false;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        if(type != LUA_TTABLE)
        {
            qWarning("[QtLua] Trying to set index in invalid variable");
            return false;
        }
        lua_pushlstring(L, key, qstrlen(key));
        value.data->push(L);
        lua_rawset(L, data->u.m_handle);
        return true;
    } else if(data->m_type == LUA_TTABLE) {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        data->push(L);
        lua_pushlstring(L, key, qstrlen(key));
        value.data->push(L);
        lua_rawset(L, -3);
        lua_pop(L, 1);
        return true;
    } else {
        qWarning("[QtLua] Trying to set index in invalid variable");
    }
    return false;
}

QString QLuaScriptValue::toUnicode() const
{
    if(!data)
        return QString();
    QByteArray utf8_str(toString());
    return QString::fromUtf8(utf8_str.constData(), utf8_str.size());
}

QVariantMap QLuaScriptValue::toMap() const
{
    if(!isTable())
        return QVariantMap();
    QVariantMap map;
    lua_State * L = data->m_engine->L;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        lua_pushnil(L);
        while(lua_next(L, data->u.m_handle) != 0) {
            QLuaScriptValue key(data->newEmpty());
            key.data->fromStack(-2);
            QLuaScriptValue value(data->newEmpty());
            value.data->fromStack(-1);
            map.insert(key.toUnicode(), value.toVariant());
            lua_pop(L, 1);
        }
    } else {
        data->push(L);
        lua_pushnil(L);
        while(lua_next(L, -2) != 0) {
            QLuaScriptValue key(data->newEmpty());
            key.data->fromStack(-2);
            QLuaScriptValue value(data->newEmpty());
            value.data->fromStack(-1);
            map.insert(key.toUnicode(), value.toVariant());
            lua_pop(L, 1);
        }
        lua_pop(L, 1);
    }

    return map;
}

QVariantHash QLuaScriptValue::toHash() const
{
    if(!isTable())
        return QVariantHash();
    QVariantHash map;
    lua_State * L = data->m_engine->L;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        lua_pushnil(L);
        while(lua_next(L, data->u.m_handle) != 0) {
            QLuaScriptValue key(data->newEmpty());
            key.data->fromStack(-2);
            QLuaScriptValue value(data->newEmpty());
            value.data->fromStack(-1);
            map.insert(key.toUnicode(), value.toVariant());
            lua_pop(L, 1);
        }
    } else {
        data->push(L);
        lua_pushnil(L);
        while(lua_next(L, -2) != 0) {
            QLuaScriptValue key(data->newEmpty());
            key.data->fromStack(-2);
            QLuaScriptValue value(data->newEmpty());
            value.data->fromStack(-1);
            map.insert(key.toUnicode(), value.toVariant());
            lua_pop(L, 1);
        }
        lua_pop(L, 1);
    }

    return map;
}

QVariantList QLuaScriptValue::toList() const
{
    if(!isTable())
        return QVariantList();
    QVariantList list;
    lua_State * L = data->m_engine->L;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        lua_pushnil(L);
        while(lua_next(L, data->u.m_handle) != 0) {
            QLuaScriptValue value(data->newEmpty());
            value.data->fromStack(-1);
            list << value.toVariant();
            lua_pop(L, 1);
        }
    } else {
        data->push(L);
        lua_pushnil(L);
        while(lua_next(L, -2) != 0) {
            QLuaScriptValue value(data->newEmpty());
            value.data->fromStack(-1);
            list << value.toVariant();
            lua_pop(L, 1);
        }
        lua_pop(L, 1);
    }

    return list;
}

QStringList QLuaScriptValue::toStringList() const
{
    if(!isTable())
        return QStringList();
    QStringList list;
    lua_State * L = data->m_engine->L;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        lua_pushnil(L);
        while(lua_next(L, data->u.m_handle) != 0) {
            QLuaScriptValue value(data->newEmpty());
            value.data->fromStack(-1);
            list << value.toString();
            lua_pop(L, 1);
        }
    } else {
        data->push(L);
        lua_pushnil(L);
        while(lua_next(L, -2) != 0) {
            QLuaScriptValue value(data->newEmpty());
            value.data->fromStack(-1);
            list << value.toString();
            lua_pop(L, 1);
        }
        lua_pop(L, 1);
    }

    return list;
}

bool QLuaScriptValue::toBool() const
{
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine || !data->m_engine->L)
            return false;
        lua_State * L = data->m_engine->L;
        if(lua_isboolean(L, data->u.m_handle))
            return lua_toboolean(L, data->u.m_handle);
    } else if(data->m_type == QLuaScriptValueData::L_TBOOLEAN) {
        return data->u.m_bool;
    }
    return toVariant().toBool();
}

qreal QLuaScriptValue::toNumber() const
{
    if(!data)
        return 0;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine || !data->m_engine->L)
            return 0;
        lua_State * L = data->m_engine->L;
        if(lua_isnumber(L, data->u.m_handle))
            return lua_tonumber(L, data->u.m_handle);
    } else if(data->m_type == QLuaScriptValueData::L_TNUMBER) {
        return data->u.m_real;
    } else if(data->m_type == QLuaScriptValueData::L_TINTERGER) {
        return data->u.m_int;
    }
    return toVariant().toDouble();
}

int QLuaScriptValue::toInteger() const
{
    if(!data)
        return 0;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine || !data->m_engine->L)
            return 0;
        lua_State * L = data->m_engine->L;
        if(lua_isnumber(L, data->u.m_handle))
            return lua_tointeger(L, data->u.m_handle);
    } else if(data->m_type == QLuaScriptValueData::L_TNUMBER) {
        return data->u.m_real;
    } else if(data->m_type == QLuaScriptValueData::L_TINTERGER) {
        return data->u.m_int;
    }
    return toVariant().toDouble();
}

// Requires pushed:
// - Data
static QByteArray qtlua_to_string(lua_State * L, QLuaScriptEnginePrivate * engine, int index)
{
    lua_getglobal(L, "tostring");
    lua_pushvalue(L, index);

    int pcall_result = lua_pcall(L, 1, 1, 0);

    if(pcall_result != 0)
    {
        QLuaException ex(engine->q_ptr->newException(QLuaScriptValue::newFromStack(engine->q_ptr, -1)));
        lua_pop(L, 1);
        engine->m_lastException = ex;
        lua_gc(L, LUA_GCCOLLECT, 0);
        return QByteArray();
    }

    size_t len = 0;
    const char * str = lua_tolstring(L, -1, &len);

    QByteArray result(str, len);

    lua_pop(L, 1);
    return result;
}

QByteArray QLuaScriptValue::toString() const
{
    if(!data)
        return QByteArray();

    if(data->m_type == LUA_TSTRING)
        return data->m_str;

    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine || !data->m_engine->L)
            return QByteArray();
        lua_State * L = data->m_engine->L;               
        if(lua_isstring(L, data->u.m_handle))
        {
            size_t len = 0;
            const char * str = lua_tolstring(L, data->u.m_handle, &len);
            return QByteArray(str, len);
        } else {
            return qtlua_to_string(L, data->m_engine, data->u.m_handle);
        }
    }

    if(!data->m_engine || !data->m_engine->L)
        return QByteArray();

    lua_State * L = data->m_engine->L;
    data->push(L);

    QByteArray result(qtlua_to_string(L, data->m_engine, lua_gettop(L)));

    lua_pop(L, 1);

    return result;
}

void * QLuaScriptValue::toLightUserData() const
{
    if(!isLightUserData())
        return NULL;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        lua_State * L = data->m_engine->L;
        if(lua_type(L, data->u.m_handle) != LUA_TLIGHTUSERDATA)
            return 0;
        return lua_touserdata(L, data->u.m_handle);
    } else {
        return data->u.m_ptr;
    }
}

void * QLuaScriptValue::toUserData() const
{
    if(!isUserData()) {
        return NULL;
    }
    lua_State * L = data->m_engine->L;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        return lua_touserdata(L, data->u.m_handle);
    } else {
//#ifdef QT_DEBUG
//        qDebug() << ">>> Lua get ref" << data->u.m_handle;
//#endif
        lua_getref(L, data->u.m_handle);
        void * ptr = lua_touserdata(L, -1);
        lua_pop(L, 1);
        return ptr;
    }
}

int QLuaScriptValue::nativeType() const
{
    if(!data)
        return LUA_TNONE;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF) {
        if(!data->m_engine || !data->m_engine->L)
            return LUA_TNONE;
        lua_State * L = data->m_engine->L;
        return lua_type(L, data->u.m_handle);
    }
    if(data->m_type == QLuaScriptValueData::L_TINTERGER)
        return LUA_TNUMBER;
    return data->m_type;
}

bool QLuaScriptValue::strictlyEquals(const QLuaScriptValue& other) const
{
    if(!data || !other.data)
        return false;
    if(data.data() == other.data.data())
        return true;
    if(!isValid() || !other.isValid())
        return false;
    if(nativeType() != other.nativeType())
        return false;
    lua_State * L = data->m_engine->L;
    data->push(L);
    other.data->push(L);
    int rq = lua_equal(L, -2, -1);
    lua_pop(L, 2);
    return rq == 1;
}

bool QLuaScriptValue::equals(const QLuaScriptValue& other) const
{
    if(!data || !other.data)
        return false;
    if(data.data() == other.data.data())
        return true;
    if(!isValid() || !other.isValid())
        return false;
    lua_State * L = data->m_engine->L;
    data->push(L);
    other.data->push(L);
    int rq = lua_equal(L, -2, -1);
    lua_pop(L, 2);
    return rq == 1;
}

bool QLuaScriptValue::lessThan(const QLuaScriptValue& other) const
{
    if(!data || !other.data)
        return false;
    if(data.data() == other.data.data())
        return true;
    if(!isValid() || !other.isValid())
        return false;
    lua_State * L = data->m_engine->L;
    data->push(L);
    other.data->push(L);
    int rq = lua_lessthan(L, -2, -1);
    lua_pop(L, 2);
    return rq == 1;
}

bool QLuaScriptValue::operator < (const QLuaScriptValue& other) const
{
    return lessThan(other);
}

bool QLuaScriptValue::operator == (const QLuaScriptValue& other) const
{
    return equals(other);
}

bool QLuaScriptValue::operator != (const QLuaScriptValue& other) const
{
    return !equals(other);
}

uint QLuaScriptValue::hashCode() const
{
    if(!isValid())
        return 0;
    if(data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        lua_State * L = data->m_engine->L;
        int type = lua_type(L, data->u.m_handle);
        switch(type)
        {
        case QLuaScriptValueData::L_TNONE:
            return 0;
        case QLuaScriptValueData::L_TNIL:
            return 0;
        case QLuaScriptValueData::L_TBOOLEAN:
            return qHash(lua_toboolean(L, data->u.m_handle));
        case QLuaScriptValueData::L_TLIGHTUSERDATA:
            return qHash(lua_touserdata(L, data->u.m_handle));
        case QLuaScriptValueData::L_TNUMBER:
            return qHash(lua_tointeger(L, data->u.m_handle));
        case QLuaScriptValueData::L_TSTRING: {
            size_t len = 0;
            const char * ptr = lua_tolstring(L, data->u.m_handle, &len);
            return qHash(QByteArray::fromRawData(ptr, len));
            }
        case QLuaScriptValueData::L_TTABLE:
        case QLuaScriptValueData::L_TFUNCTION:
        case QLuaScriptValueData::L_TUSERDATA:
        case QLuaScriptValueData::L_TTHREAD:
            return 0;
        default:
            Q_ASSERT_X(0, "QLuaScriptValue::hashCode()", "Invalid lua variable type");
            return 0;
        }
    } else {
        switch(data->m_type)
        {
        case QLuaScriptValueData::L_TNONE:
            return 0;
        case QLuaScriptValueData::L_TNIL:
            return 0;
        case QLuaScriptValueData::L_TBOOLEAN:
            return qHash(data->u.m_bool);
        case QLuaScriptValueData::L_TLIGHTUSERDATA:
            return qHash(data->u.m_ptr);
        case QLuaScriptValueData::L_TNUMBER:
            return qHash(uint(data->u.m_real));
        case QLuaScriptValueData::L_TSTRING:
            return qHash(data->m_str);
        case QLuaScriptValueData::L_TTABLE:
        case QLuaScriptValueData::L_TFUNCTION:
        case QLuaScriptValueData::L_TUSERDATA:
        case QLuaScriptValueData::L_TTHREAD:
            return 0;
        case QLuaScriptValueData::L_TINTERGER:
            return qHash(data->u.m_int);
        default:
            Q_ASSERT_X(0, "QLuaScriptValue::hashCode()", "Invalid lua variable type");
            return 0;
        }
    }
}

QLuaScriptValue QLuaScriptValue::metaTable() const
{
    if(!data || !data->m_engine || !data->m_engine->L)
        return QLuaScriptValue();
    lua_State * L = data->m_engine->L;
    data->push(L);
    if(lua_getmetatable(L, -1) == 0)
    {
        lua_pop(L, 1);
        return QLuaScriptValue();
    }
    QLuaScriptValue rv(QLuaScriptValue::newFromStack(data->m_engine->q_ptr, -1));
    lua_pop(L, 2);
    return rv;
}

void QLuaScriptValue::setMetaTable(const QLuaScriptValue& value)
{
    if(!data || !data->m_engine || !data->m_engine->L || !value.isValid())
        return;
    lua_State * L = data->m_engine->L;
    data->push(L);
    value.data->push(L);
    lua_setmetatable(L, -2);
    lua_pop(L, 1);
}

void QLuaScriptValue::pushToStack(QLuaScriptEngine * engine) const
{
    if(!data || data->m_type == LUA_TNONE)
        return;
    if(data->m_engine && data->m_engine->L) {
        data->push(data->m_engine->L);
    } else if(engine) {
        data->push(QLuaScriptEnginePrivate::priv(engine)->L);
    } else {
        Q_ASSERT_X(0, "QLuaScriptValue::pushToStack", "No proper pointer to Lua engine provided");
    }
}

void * QLuaScriptValue::lua_cast_helper(int type_id) const
{
    void * ptr = toUserData();
    if(!ptr)
        return NULL;
    if(data->m_engine->m_ptrMetaTypes[ptr] == type_id)
        return ptr;
    return NULL;
}

static inline QLatin1String lua_type_name(int type)
{
    switch(type)
    {
    case LUA_TNONE: return QLatin1String("LUA_TNONE");
    case LUA_TNIL: return QLatin1String("LUA_TNIL");
    case LUA_TBOOLEAN: return QLatin1String("LUA_TBOOLEAN");
    case LUA_TLIGHTUSERDATA: return QLatin1String("LUA_TLIGHTUSERDATA");
    case LUA_TNUMBER: return QLatin1String("LUA_TNUMBER");
    case LUA_TSTRING: return QLatin1String("LUA_TSTRING");
    case LUA_TTABLE: return QLatin1String("LUA_TTABLE");
    case LUA_TFUNCTION: return QLatin1String("LUA_TFUNCTION");
    case LUA_TUSERDATA: return QLatin1String("LUA_TUSERDATA");
    case LUA_TTHREAD: return QLatin1String("LUA_TTHREAD");
    case QLuaScriptValueData::L_TINTERGER: return QLatin1String("LUA_TNUMBER");
    default: return QLatin1String("Uknown");
    }
}

QDebug operator << (QDebug d, const QLuaScriptValue& value)
{
    if(!value.isValid())
        return d << "QLuaScriptValue(None)";
    d << "QLuaScriptValue(";

    if(value.isUserData()) {
        void * ptr = value.toUserData();
        d << "LUA_TUSERDATA, Ptr=" << ptr << ",Class name ="
          << QMetaType::typeName(value.data->m_engine->m_ptrMetaTypes.value(ptr));

        if(value.data->m_engine->m_ptrMetaTypes.value(ptr) ==
                qMetaTypeId<QLuaInternal::QLuaQObjectWrapper>())
        {
            QLuaInternal::QLuaQObjectWrapper * wrap = qtlua_scriptvalue_cast<QLuaInternal::QLuaQObjectWrapper>(value);
            d << ", QObject is" << wrap->m_objectPtr;
        }

        d << ")";
        return d;
    }

    if(value.data->m_type == QLuaScriptValueData::L_TSTACKREF)
    {
        lua_State * L = value.data->m_engine->L;
        int l_type = lua_type(L, value.data->u.m_handle);
        d << lua_type_name(l_type);
        d << ",";
        d << value.toUnicode();
    } else {
        d << lua_type_name(value.data->m_type) << "," << value.toUnicode();
    }
    return d << ")";
}
